package com.free.semantic.utils;

import com.free.semantic.common.*;
import com.free.semantic.core.*;
import com.free.semantic.core.funcimpl.ConstMapImpl;
import com.free.semantic.core.funcimpl.MultipleKeyFuncImpl;
import com.free.semantic.core.funcimpl.MultipleKeyMap;
import com.free.semantic.core.funcimpl.SourceMapImpl;
import com.free.semantic.core.impl.ConceptInstanceManageImpl;
import com.free.semantic.core.impl.ConceptManageImpl;
import com.free.semantic.core.proxy.ProxyFunctionProvider;
import com.free.semantic.core.script.SemanticNetEngine;
import com.free.semantic.core.script.impl.ScriptEngineImpl;
import com.free.semantic.core.takeclient.ConceptInsTakeCommand;
import com.free.semantic.core.takeclient.impl.LocalManageImpl;
import com.free.semantic.req.RelaItemDef;
import com.free.semantic.req.RelationDefine;
import com.free.semantic.req.RelationDefineGroup;
import com.google.common.collect.Lists;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.free.semantic.common.Range.ONE;
import static com.free.semantic.common.RangeObj.more;
import static com.free.semantic.common.RangeObj.one;
import static com.free.semantic.core.concept.impl.ConceptSelfInfoConcept.*;

public class Utils {

    public static ConceptManage getConceptManage(RelationDefineGroup defineGroup) {
        ConceptManageImpl conceptManage = new ConceptManageImpl();
        addDefineGroup(conceptManage, defineGroup);
        return conceptManage;
    }

    //todo 缺少对重复名称的支持
    public static void mergerConcept(ConceptManage conceptManage, ConceptInsTakeCommand conceptInsTakeCommand) {
        List<String> rootObj = conceptInsTakeCommand.take(Ls.of(new ParamNode(ROOT_OBJECT)), more(OBJECT));
        Set<String> registerName = new HashSet<>();
        Set<RelationItem> registerRelation = new HashSet<>();
        for (String objName : rootObj) {
            buildMangeFromTake(conceptInsTakeCommand, conceptManage, objName, registerName, registerRelation);
        }
    }

    public static void mergerInstance(ConceptInstanceManage conceptManage, ConceptInstanceManage another) {
        mergerConcept(conceptManage.concept(), another.concept());
        ProxyFunctionProvider provider = new ProxyFunctionProvider(another.concept(), another);
        provider.prepareFunctionMap();
        for (Map.Entry<RelationItem, RelationFunction> functionEntry : provider.getFunctionMap().entrySet()) {
            RelationItem relationItem = functionEntry.getKey();
            RelationFunction function = functionEntry.getValue();
            conceptManage.registerFunction(relationItem.getSourceObjectList(), relationItem.getDestObject(), function);
        }
    }

    public static void mergerConcept(ConceptInstanceManage conceptManage, ConceptInstanceManage another) {
        mergerConcept(conceptManage.concept(), another.concept());
    }

    public static void equalConceptMap(ConceptInstanceManage conceptManage,
                                       List<String> sourceFrom, String destFrom,
                                       List<String> sourceTo, String destTo) {
        conceptManage.concept().conceptMergeTo(sourceFrom, destFrom, sourceTo, destTo);
        for (int i = 0; i < sourceFrom.size(); i++) {
            String s = sourceFrom.get(i);
            Utils.registerTheSameFunction(conceptManage, Ls.of(one(s)), one(sourceTo.get(i)), one(s));
        }
        Utils.registerTheSameFunction(conceptManage, Ls.of(one(destTo)), one(destFrom), one(destTo));
    }

    public static void equalConceptMapAndHideMiddle(ConceptInstanceManage conceptManage,
                                                    List<String> sourceFrom, String destFrom,
                                                    List<String> sourceTo, String destTo) {
        conceptManage.concept().conceptMergeTo(sourceFrom, destFrom, sourceTo, destTo);
        for (int i = 0; i < sourceFrom.size(); i++) {
            String s = sourceFrom.get(i);
            Utils.registerTheSameFunction(conceptManage, Ls.of(one(s)), one(sourceTo.get(i)), one(s));
        }
        Utils.registerTheSameFunction(conceptManage, Ls.of(one(destTo)), one(destFrom), one(destTo));
        conceptManage.hideObject(sourceTo);
        conceptManage.hideObject(Ls.of(destTo));
    }

    public static void mergerConcept(ConceptManage conceptManage, ConceptManage another) {
        List<String> rootObj = another.findAllRootObjectList();
        Set<String> registerName = new HashSet<>();
        Set<RelationItem> registerRelation = new HashSet<>();
        for (String objName : rootObj) {
            buildMangeFromTake(another, conceptManage, objName, registerName, registerRelation);
        }
    }

    public static void forEachRelationItem(ConceptManage conceptManage, Consumer<RelationItem> consumer) {
        for (RelationInstance instance : conceptManage.getAllInstance()) {
            for (RelationItem relationItem : instance.getRelationItems()) {
                consumer.accept(relationItem);
            }
        }
    }

    public static RangeObj getNode(BaseNode baseNode) {
        RangeObj rangeObj = new RangeObj();
        rangeObj.setRange(baseNode.getRange());
        rangeObj.setObjectName(baseNode.getNodeName());
        return rangeObj;
    }

    public static boolean isOne(Range range) {
        return range.equals(ONE);
    }

    public static boolean isMulti(Range range) {
        return !range.isIncludeEnd();
    }

    public static RelationItem findSingleItem(ConceptManage conceptManage, List<String> source, String dest) {
        List<RelationChain> chains = conceptManage.findExactRelationChain(source, dest);
        if (chains.isEmpty()) {
            throw new RuntimeException(String.format("当前语义网络中不存在关系%s->%s", String.join(",", source), dest));
        }
        if (chains.size() > 1) {
            throw new RuntimeException(String.format("实现器中关系%s->%s 在当前语义网络中匹配多个路径关系", String.join(",", source), dest));
        }
        if (chains.get(0).getRelationItems().size() != 1) {
            throw new RuntimeException(String.format("实现器中关系%s->%s 在当前语义网络中 存在跨关系注册，不允许", String.join(",", source), dest));
        }
        return chains.get(0).getRelationItems().get(0);
    }

    public static ConceptInstanceManage copy(ConceptInstanceManage conceptInstanceManage) {
        ConceptInsTakeCommand client = new LocalManageImpl(conceptInstanceManage);
        SemanticNetEngine engine = new ScriptEngineImpl();
        ConceptManage conceptManage = engine.buildManageFromTake(client);
        ProxyFunctionProvider provider = new ProxyFunctionProvider(conceptManage, conceptInstanceManage);
        provider.prepareFunctionMap();
        return new ConceptInstanceManageImpl(conceptManage, provider);
    }

    public static RelationItem findSingleItemWithoutError(ConceptManage conceptManage, List<String> source, String dest) {
        List<RelationItem> match = new ArrayList<>();
        for (RelationItem relationItem : conceptManage.getGoRelationByObject(source.get(0))) {
            if (!relationItem.getDestObj().equals(dest)) {
                continue;
            }
            List<String> itemSourceObjList = relationItem.getSourceObjList();
            if (new HashSet<>(itemSourceObjList).equals(new HashSet<>(source))) {
                match.add(relationItem);
            }
        }
        if (match.size() != 1) {
            return null;
        }
        return match.get(0);
    }

    private static void buildMangeFromTake(ConceptInsTakeCommand conceptInsTakeCommand, ConceptManage conceptManage,
                                           String objectName, Set<String> registerName, Set<RelationItem> registerRelation) {

        List<String> relateRelation = conceptInsTakeCommand.take(Ls.of(new ParamNode(OBJECT, objectName)), more(RELATION));
        List<RelationItem> items = new ArrayList<>();

        List<String> currentAddedNameList = new ArrayList<>();

        for (String relation : relateRelation) {
            RelationItem relationItem = RelationItem.parseFromStr(relation);
            if (registerRelation.contains(relationItem)) {
                continue;
            }
            registerRelation.add(relationItem);
            for (String sourceName : relationItem.getSourceObjList()) {
                if (!registerName.contains(sourceName)) {
                    registerName.add(sourceName);
                    conceptManage.addConcept(sourceName);

                    currentAddedNameList.add(sourceName);
                }
            }
            if (!registerName.contains(relationItem.getDestObj())) {
                registerName.add(relationItem.getDestObj());
                conceptManage.addConcept(relationItem.getDestObj());
                currentAddedNameList.add(relationItem.getDestObj());
            }
            items.add(relationItem);
        }
        boolean success = conceptManage.instanceRelation(items);
        if (!success) {
            throw new RuntimeException(String.format("创建关系%s失败", String.join(",", relateRelation)));
        }
        for (String name : currentAddedNameList) {
            buildMangeFromTake(conceptInsTakeCommand, conceptManage, name, registerName, registerRelation);
        }
    }

    private static void buildMangeFromTake(ConceptManage another, ConceptManage conceptManage,
                                           String objectName, Set<String> registerName, Set<RelationItem> registerRelation) {

        List<RelationItem> goRelationByObject = another.getGoRelationByObject(objectName);

        List<RelationItem> backRelationByObject = another.getBackRelationByObject(objectName);

        goRelationByObject.addAll(backRelationByObject);

        List<RelationItem> items = new ArrayList<>();

        List<String> currentAddedNameList = new ArrayList<>();

        for (RelationItem relationItem : goRelationByObject) {
            if (registerRelation.contains(relationItem)) {
                continue;
            }
            registerRelation.add(relationItem);
            for (String sourceName : relationItem.getSourceObjList()) {
                if (!registerName.contains(sourceName)) {
                    registerName.add(sourceName);
                    conceptManage.addConcept(sourceName);

                    currentAddedNameList.add(sourceName);
                }
            }
            if (!registerName.contains(relationItem.getDestObj())) {
                registerName.add(relationItem.getDestObj());
                conceptManage.addConcept(relationItem.getDestObj());
                currentAddedNameList.add(relationItem.getDestObj());
            }
            items.add(relationItem);
        }
        boolean success = conceptManage.instanceRelation(items);
        if (!success) {
            throw new RuntimeException(String.format("创建关系%s失败", items.stream().map(RelationItem::toString).collect(Collectors.joining(","))));
        }
        for (String name : currentAddedNameList) {
            buildMangeFromTake(another, conceptManage, name, registerName, registerRelation);
        }
    }

    public static void createRelation(ConceptManage manage, String relationName, String A, String B) {
        boolean added = manage.instanceRelation(relationName, new HashMap<String, String>() {
            {
                put("A", A);
                put("B", B);
            }
        });
        if (!added) {
            throw new RuntimeException("关系重复定义");
        }
    }

    public static void createRelationSameMapping(ConceptInstanceManage manage, String A, String B) {
        boolean added = manage.concept().instanceRelation(FIND_ONE, new HashMap<String, String>() {
            {
                put("A", A);
                put("B", B);
            }
        });
        if (!added) {
            throw new RuntimeException("关系重复定义");
        }
        registerTheSameFunction(manage, Ls.of(one(A)), one(B), one(A));
    }

    public static <T> void createRelationConstMapping(ConceptInstanceManage manage, String A, String B, T value) {
        boolean added = manage.concept().instanceRelation(FIND_ONE, new HashMap<String, String>() {
            {
                put("A", A);
                put("B", B);
            }
        });
        if (!added) {
            throw new RuntimeException("关系重复定义");
        }
        registerConstFunction(manage, Ls.of(one(A)), one(B), value);
    }

    public static FunctionProvider buildMap(String source, String dest, String[][] map) {
        return relationItem -> {
            if (match(relationItem.getSourceObjectList(), source) && match(relationItem.getDestObject(), dest)) {
                return buildFunc(source, dest, map, false);
            }
            return null;
        };
    }

    public static boolean match(RangeObj rangeObjs, String name) {
        Set<String> actual = new LinkedHashSet<>();
        actual.add(rangeObjs.getObjectName());
        Set<String> want = new LinkedHashSet<>(Arrays.asList(name));
        return actual.equals(want);
    }

    public static boolean match(List<RangeObj> rangeObjs, String... name) {
        Set<String> actual = new LinkedHashSet<>();
        for (RangeObj rangeObj : rangeObjs) {
            actual.add(rangeObj.getObjectName());
        }
        Set<String> want = new LinkedHashSet<>(Arrays.asList(name));
        return actual.equals(want);
    }

    public static RelationFunction buildFunc(String source, String dest, String[][] map, boolean revert) {
        Map<String, List<String>> keyResMap = new HashMap<>();
        if (revert) {
            for (String[] row : map) {
                if (keyResMap.containsKey(row[1])) {
                    keyResMap.get(row[1]).add(row[0]);
                } else {
                    keyResMap.put(row[1], Lists.newArrayList(row[0]));
                }
            }

        } else {
            for (String[] row : map) {
                if (keyResMap.containsKey(row[0])) {
                    keyResMap.get(row[0]).add(row[1]);
                } else {
                    keyResMap.put(row[0], Lists.newArrayList(row[1]));
                }
            }
        }
        if (revert) {
            return new SimpleRelationFuncImpl(dest, source, keyResMap);
        } else {
            return new SimpleRelationFuncImpl(source, dest, keyResMap);
        }
    }

    public static FunctionProvider buildMap(String[] source, String dest, String[][] map) {
        return relationItem -> {
            if (match(relationItem.getSourceObjectList(), source) && match(relationItem.getDestObject(), dest)) {
                return buildFunc(source, dest, map);
            }
            return null;
        };
    }

    public static RelationFunction buildFunc(String[] source, String dest, String[][] map) {
        return new MultipleKeyFuncImpl(source, dest, MultipleKeyMap.fromArray(map));
    }

    public static class SimpleRelationFuncImpl implements RelationFunction {

        private String source;

        private String dest;

        private Map<String, List<String>> keyResMap;

        public SimpleRelationFuncImpl(String source, String dest, Map<String, List<String>> keyResMap) {
            this.source = source;
            this.dest = dest;
            this.keyResMap = keyResMap;
        }

        @Override
        public void calculate(Context context) {
            List<String> listValue = context.getListValue(source);
            List<String> result = new ArrayList<>();
            for (String val : listValue) {
                if (!keyResMap.containsKey(val)) {
                    throw new RuntimeException(String.format("不包含类型为 %s, 值为 %s 的事物", source, val));
                }
                result.addAll(keyResMap.get(val));
            }
            context.setValue(dest, result);
        }
    }

    private static final String[] key = new String[]{"A", "B", "C", "D", "E", "F"};

    public static void createRelation2(ConceptManage manage, String relationName, String... params) {
        HashMap<String, String> hashMap = new HashMap<>();
        for (int i = 0; i < params.length; i++) {
            hashMap.put(key[i], params[i]);
        }
        boolean add = manage.instanceRelation(relationName, hashMap);
        if (!add) {
            throw new RuntimeException(String.format("实例化关系%s失败", relationName));
        }
    }

    public static void registerTheSameFunction(ConceptInstanceManage instanceManage, String sourceObj, String destObj) {
        instanceManage.registerFunction(Ls.of(sourceObj), destObj, new SourceMapImpl(sourceObj));
    }

    public static void registerTheSameFunction(ConceptInstanceManage instanceManage, List<String> allSource, String destObj, String valueObj) {
        instanceManage.registerFunction(allSource, destObj, new SourceMapImpl(valueObj));
    }

    public static void registerTheSameFunction(ConceptInstanceManage instanceManage, List<RangeObj> allSource, RangeObj destObj, RangeObj valueObj) {
        instanceManage.registerFunction(allSource, destObj, new SourceMapImpl(valueObj.getObjectName()));
    }

    public static <T> void registerConstFunction(ConceptInstanceManage instanceManage, List<RangeObj> allSource, RangeObj destObj, T value) {
        instanceManage.registerFunction(allSource, destObj, new ConstMapImpl(value));
    }

    public static <T> void registerConstFunction(ConceptInstanceManage instanceManage, List<String> allSource, String destObj, T value) {
        instanceManage.registerFunction(allSource, destObj, new ConstMapImpl(value));
    }

    public static <T> void registerEmptyFunction(ConceptInstanceManage instanceManage, List<RangeObj> allSource, RangeObj destObj) {
        instanceManage.registerFunction(allSource, destObj, context -> {
        });
    }

    public static void object(ConceptManage manage, String... objectLs) {
        manage.addConcept(objectLs);
    }

    public static void defineRelation(RelationDefineGroup relationGroup, String relationName, RelaItemDef... relaItemDefs) {
        relationGroup.addDefine(relationName, Ls.of(relaItemDefs));
    }

    public static void addRelationDef(ConceptManage manage, String relationName,
                                      RelaItemDef... relaItemDefs) {
        manage.addRelationDefine(relationName, Ls.of(relaItemDefs));
    }

    public static RelaItemDef defineItem(List<RangeObj> from, List<RangeObj> to) {
        assert to.size() == 1;
        return new RelaItemDef(from, to.get(0));
    }

    public static List<RangeObj> oneObj(String... objName) {
        List<RangeObj> res = new ArrayList<>();
        for (String s : objName) {
            res.add(one(s));
        }
        return res;
    }

    public static List<RangeObj> lessOneObj(String... objName) {
        List<RangeObj> res = new ArrayList<>();
        for (String s : objName) {
            res.add(RangeObj.lessOne(s));
        }
        return res;
    }

    public static List<RangeObj> moreObj(String... objName) {
        List<RangeObj> res = new ArrayList<>();
        for (String s : objName) {
            res.add(more(s));
        }
        return res;
    }

    public static void addDefineGroup(ConceptManage conceptManage, RelationDefineGroup defineGroup) {
        for (RelationDefine relationDefine : defineGroup.getRelationDefines()) {
            conceptManage.addRelationDefine(relationDefine.getName(), relationDefine.getRelaItemDefs());
        }
    }

}
